﻿// The MIT License(MIT)
//
// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#pragma warning disable IDE0046 // Convert to conditional expression

using System.Text;
using LiveChartsGenerators.Definitions;
using Microsoft.CodeAnalysis;

namespace LiveChartsGenerators.Templates;

public static class MotionPropertyTemplates
{
    public static string GetTemplate(MotionProperty target)
    {
        var propertyName = target.Property.Name;

        var type = target.Property.ContainingType;

        var displayFormat = new SymbolDisplayFormat(
            typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
            genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters);

        var declaringType = type.ToDisplayString(displayFormat);

        var propertyType = target.Property.Type.ToDisplayString();

        var sanitizedPropertyType = target.Property.Type.IsReferenceType && propertyType.EndsWith("?")
            ? propertyType.Substring(0, propertyType.Length - 1)
            : propertyType;

        return @$"// <auto-generated>
//     This code was generated by a LiveCharts source generator, do not edit.
// </auto-generated>

#nullable enable

namespace {type.ContainingNamespace};

{GetFormattedAccessibility(type.DeclaredAccessibility)} partial class {declaringType}
{{
    /// <summary>
    ///    The <see cref=""{propertyName}""/> property definition.
    /// </summary>
    public static readonly LiveChartsCore.Motion.PropertyDefinition {propertyName}Property = new(
        propertyName:   ""{propertyName}"",
        propertyType:   typeof({sanitizedPropertyType}),
        getter:         (g)     =>   (({declaringType})g).{propertyName},
        setter:         (g, v)  =>   (({declaringType})g).{propertyName} = v is {sanitizedPropertyType} s ? s : (({declaringType})g)._{propertyName}MotionProperty.DefaultValue,
        motionGetter:   (g)     =>   (({declaringType})g)._{propertyName}MotionProperty);

    private readonly {GetMotionPropertyType(target.Property.Type)} _{propertyName}MotionProperty = new();

    /// <summary>
    ///     The <see cref=""{propertyName}""/> motion property.
    /// </summary>
    /// <remarks>
    ///     This property is used to animate the <see cref=""{propertyName}""/> property.
    /// </remarks>
    {GetFormattedAccessibility(target.Property.DeclaredAccessibility)} partial {target.Property.Type.ToDisplayString()} {propertyName}
    {{
        {GetGetter(target)}
        {GetSetter(target)}
    }}

    partial void On{target.Property.Name}Changed({target.Property.Type.ToDisplayString()} value);
}}
";
    }

    public static string GetClassTemplate(IGrouping<ISymbol?, MotionProperty?> group)
    {
        var containingType = group.Key!; // ! not nullable at this point.

        var displayFormat = new SymbolDisplayFormat(
            typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
            genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters);

        var displayFormat2 = new SymbolDisplayFormat(
            typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
            genericsOptions: SymbolDisplayGenericsOptions.None);

        var baseType = (containingType as INamedTypeSymbol)?.BaseType;

        if (baseType is null)
            return
                $"An error ocurred, the type {containingType.ToDisplayString(displayFormat)}" +
                $" must derive at least from Animatable.";

        var sb = new StringBuilder();

        foreach (var item in group)
        {
            if (item is not { } property) continue;
            _ = sb.AppendLine(@$"            [""{property.Property.Name}""] = {property.Property.Name}Property,");
        }
        var propertyDefinitions = sb.ToString();

        return @$"// <auto-generated>
//     This code was generated by a LiveCharts source generator, do not edit.
// </auto-generated>

namespace {containingType.ContainingNamespace};

{GetFormattedAccessibility(containingType.DeclaredAccessibility)} partial class {containingType.ToDisplayString(displayFormat)}
{{
    /// <summary>
    /// Gets the <see cref=""LiveChartsCore.Motion.PropertyDefinition""/> collection in the type.
    /// </summary>
    public static new System.Collections.Generic.Dictionary<string, LiveChartsCore.Motion.PropertyDefinition> PropertyDefinitions {{ get; }} =
        Merge(new()
        {{
{propertyDefinitions}        }},
        {baseType.ToDisplayString(displayFormat2)}.PropertyDefinitions);

    /// <summary>
    /// Gets the motion property definitions in the instance.
    /// </summary>
    /// <returns>The propertt definitions.</returns>
    protected override System.Collections.Generic.Dictionary<string, LiveChartsCore.Motion.PropertyDefinition> GetPropertyDefinitions() => PropertyDefinitions;
}}";
    }

    private static string GetGetter(MotionProperty target)
    {
        // propertySymbol.GetMethod.IsImplicitlyDeclared
        // it seems that it does not work as expected on partial properties,
        // lets instead use the HasExplicitAcesseors property in the attribute.

        return target.Property.GetMethod is null
            ? string.Empty
            : !target.HasExplicitAcessors
                ? $"get => _{target.Property.Name}MotionProperty.GetMovement(this);"
                : "get;";
    }

    private static string GetSetter(MotionProperty target)
    {
        return target.Property.SetMethod is null
            ? string.Empty
            : !target.HasExplicitAcessors
                ? $"set {{ _{target.Property.Name}MotionProperty.SetMovement(value, this); On{target.Property.Name}Changed(value); }}"
                : "set;";
    }

    private static string GetFormattedAccessibility(Accessibility accessibility)
    {
        return accessibility switch
        {
            Accessibility.Public => "public",
            Accessibility.Private => "private",
            Accessibility.Internal => "internal",
            Accessibility.Protected => "protected",
            Accessibility.ProtectedAndInternal => "protected internal",
            Accessibility.ProtectedOrInternal => "private protected",
            Accessibility.NotApplicable => "not applicable",
            _ => "unknown"
        };
    }

    private static string GetMotionPropertyType(ITypeSymbol type)
    {
        return type.ToDisplayString() switch
        {
            "float" => "LiveChartsCore.Motion.FloatMotionProperty",
            "double" => "LiveChartsCore.Motion.DoubleMotionProperty",
            "double?" => "LiveChartsCore.Motion.NullableDoubleMotionProperty",
            "LiveChartsCore.Drawing.LvcColor" => "LiveChartsCore.Motion.ColorMotionProperty",
            "LiveChartsCore.Drawing.LvcPoint" => "LiveChartsCore.Motion.PointMotionProperty",
            "LiveChartsCore.Drawing.LvcSize" => "LiveChartsCore.Motion.SizeMotionProperty",
            "LiveChartsCore.Drawing.Padding" => "LiveChartsCore.Motion.PaddingMotionProperty",
            "LiveChartsCore.Painting.Paint" => "LiveChartsCore.Motion.PaintMotionProperty",
            "LiveChartsCore.Painting.Paint?" => "LiveChartsCore.Motion.PaintMotionProperty",
            "LiveChartsCore.Drawing.LvcDropShadow" +
            "" => "LiveChartsCore.Motion.DropShadowMotionProperty",
            "LiveChartsCore.Drawing.LvcDropShadow?" => "LiveChartsCore.Motion.DropShadowMotionProperty",
            "SkiaSharp.SKMatrix" => "LiveChartsCore.SkiaSharpView.Motion.SKMatrixMotionProperty",
            _ => "UnsuportedType"
        };
    }
}
